Skip to content

feat(v0.2): UI modes wiring + auto-theme + error catalog + /search + /palette#5

Merged
SimpleLittleDev merged 3 commits intomainfrom
devin/1777808535-v0.2-modes-virtual-search-errors
May 3, 2026
Merged

feat(v0.2): UI modes wiring + auto-theme + error catalog + /search + /palette#5
SimpleLittleDev merged 3 commits intomainfrom
devin/1777808535-v0.2-modes-virtual-search-errors

Conversation

@SimpleLittleDev
Copy link
Copy Markdown
Owner

@SimpleLittleDev SimpleLittleDev commented May 3, 2026

Summary

Stacked on top of #4. Closes out the remaining v0.2 phases — Phase 7 (display modes), Phase 8 (long-output protection), Phase 9 (search + palette), Phase 10 (error catalog). All 184 tests pass; lint, typecheck, and build are clean.

Phase 7 — Display modes + auto-theme

  • src/tui/DebugPanel.tsx — bordered panel showing FSM state, terminal caps, MCP server count, and a flags row. Mounts only when uiMode === "debug", so production sessions stay clean.
  • src/app.tsx:
    • compact and focus hide the welcome banner.
    • focus also hides the StatusBar, the LogoAnimator, and the bottom command-count hint — pure chat + prompt for serious coding sessions.
    • debug adds the <DebugPanel> above the logo with FSM / caps / MCP counts / browser engine kind / transcript length.
    • noanim already wired through stable flags and now propagates to PromptV2 too.
  • Auto theme selection — when config.theme is still the stock default, pickThemeForCaps() upgrades to nocolor (when NO_COLOR / dumb terminal) or termux (when running on Android) based on TerminalCapabilities. Explicit /theme choices are honored unchanged.

Phase 8 — Long output protection

  • src/lib/transcriptVirtual.ts:
    • virtualize<T>(items, { maxRender, keepRecent }) returns { visible, dropped } for future render-time virtualization.
    • clipLines(text, maxLines, marker?) clips a long string to N lines with a configurable trailer like … 412 more lines (use /show last).
  • src/app.tsx — every tool-call result now goes through clipLines(result.output, 200) before reaching the chat bubble. Stops a 50k-line stack trace from blowing up the TUI; the full text is still in the chat history for /save / future inspection.

Phase 9 — Search + palette

  • src/commands/search.ts/search <query> (aliases find, history-search) reads ~/.fastcode/history JSONL directly, fuzzy-matches on substring, and renders the 10 most recent hits with timestamps and one-line previews. Works even on a fresh restart before the live history ref has anything.
  • src/commands/palette.ts/palette (aliases commands, cmds) groups every visible command into 5 buckets (Session, Editing, Provider, Sandbox + MCP, Misc) and lists each with its aliases and description. Textual fallback for a future Ctrl+P overlay.

Phase 10 — Error catalog

  • src/lib/errorCatalog.ts — small classifier matching common error shapes:
    • FS: FS_NOT_FOUND (ENOENT), FS_DENIED (EACCES)
    • Net: NET_TIMEOUT (ETIMEDOUT/timeout), NET_DNS (ENOTFOUND/getaddrinfo), PIPE_BROKEN (EPIPE)
    • Sandbox: SANDBOX_DENIED (anything with SANDBOX_* prefix)
    • MCP: MCP_TRANSPORT (json-rpc / mcp keywords)
    • Provider/auth: AUTH_FAILED (401/403), RATE_LIMITED (429), PROVIDER_5XX
    • Generic: ABORTED, UNKNOWN fallback
      Each entry returns { code, title, detail, hint? }. formatFriendlyError() renders to markdown for the chat layer.
  • src/app.tsx — both tool failures and chat-loop exceptions now flow through explainError() so the user sees a labelled error block (e.g. **Network timeout** (NET_TIMEOUT)\n\n_Couldn't reach api.openai.com within the timeout. Check connectivity and retry._) instead of a raw Error: ... line.

Tests added (19 new, 184 total)

  • test/transcriptVirtual.test.ts (5) — virtualize cap behavior, clipLines defaults and custom marker.
  • test/errorCatalog.test.ts (10) — every classifier branch + formatFriendlyError shape.
  • test/searchCommand.test.ts (4) — /search and /palette are reachable by name + alias and produce expected output shape.

npm run check (test + typecheck + lint + build) passes locally.

Review & Testing Checklist for Human

  • Mode switching: /mode focus, /mode compact, /mode debug, /mode noanim, then /mode normal. Confirm each visibly changes the UI (banner / status / logo / debug panel) and that re-running normal restores everything.
  • Auto-theme: launch with NO_COLOR=1 and verify the chat renders in nocolor automatically without manually running /theme nocolor. Then on Termux, verify the termux theme kicks in.
  • /search: send a few prompts, restart, and run /search <substring>. Confirm matches surface from the persisted file (not just in-memory).
  • /palette: confirm every visible command appears in exactly one bucket, with its aliases listed.
  • Long output clipping: trigger a tool that emits more than 200 lines and confirm the bubble shows the head + a … N more lines trailer instead of overflowing.
  • Error catalog: run a tool against a non-existent file or unreachable host and confirm the failure block is rendered with a friendly title + hint, not a stack trace.

Notes

Link to Devin session: https://app.devin.ai/sessions/0e73b43883c04a17b651ca6aa7dc1a5e
Requested by: @SimpleLittleDev

SimpleLittleDev and others added 3 commits May 3, 2026 11:27
Phase 0 + 1 + 3 + 4 + 5 of the v0.2 blueprint, in one shippable PR.

Core foundations
----------------
- src/core/EventBus.ts: typed pub/sub bus for cross-module events
- src/core/AgentStateMachine.ts: 6-state FSM (idle/thinking/executing/
  waiting/success/error) with explicit transitions

Terminal + TUI
--------------
- src/platform/TerminalCapabilities.ts: single source of truth for OS,
  shell, color depth, unicode, size class, SSH/CI/dumb/Termux/WSL flags
- src/tui/LogoAnimator.tsx: status-reactive living logo with 4-tier
  fidelity (Unicode-rich, Unicode-minimal, ASCII, no-anim)

Model Context Protocol (MCP)
----------------------------
- src/mcp/types.ts | config.ts | client.ts | registry.ts | permission.ts
  | manager.ts | index.ts
- Loads ~/.fastcode/mcp.json + .fastcode/mcp.json + project mcp.json
- Hand-rolled JSON-RPC stdio client (no heavy SDK dep)
- Permission classifier with low/med/high risk + auto/confirm/deny

Sandbox v2
----------
- src/sandbox/PathGuard.ts: resolve + validate every fs path through one
  guard, with deny globs and symlink defense
- src/sandbox/policy.ts: load .fastcode/policy.json, expose shell/network
  guards, default-deny destructive commands
- gitignore-style globToRegex with **/.env etc.

Browser tool
------------
- src/browser/text.ts: text-only engine on top of lib/web.ts
- New agent tool browser_fetch wired through the policy hook
- Headless engine deferred to a follow-up PR (API stable)

Agent integration
-----------------
- runAgentTool now accepts AgentToolServices ({browser, mcp, signal})
- New agent tools: browser_fetch, mcp_call (server.tool addressing)
- AGENT_SYSTEM_PROMPT advertises the new tools

Slash commands
--------------
- /mcp [status|tools|restart <s>|permission <s> <a|c|d>]
- /sandbox [status]
- /mode [normal|compact|focus|debug|noanim]

App wiring
----------
- App holds a single AgentStateMachine + capabilities snapshot
- LogoAnimator renders above the prompt, reacts to FSM state
- MCP manager auto-starts only when .fastcode/mcp.json exists
- AppActions exposes optional mcp / sandbox / setUiMode hooks

Tests (all passing — 122 total, 47 new)
---------------------------------------
- eventBus, stateMachine, terminalCaps, logoAnimator
- sandboxPath, permission, mcpConfig, mcpRegistry
- mcpClient (real stdio echo server fixture), browserText

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…dless browser

Phase 2:
- src/lib/inputHistory.ts: persistent JSONL history at ~/.fastcode/history
- src/tui/PromptV2.tsx: multiline editor (Shift+Enter), Up/Down history recall,
  paste-burst detection, slash-command autocomplete, Ctrl+U/W/A/E/K keybinds,
  cursor block highlight via ink inverse, blink toggle
- src/tui/DiffView.tsx: unified-diff parser + themed +/- rendering
- src/ui/Message.tsx: BubbleRole expanded (tool/error/warn/success/command/diff)
  with distinct sigils, color routing, inline DiffView for diff role

Phase 5b:
- src/browser/headless.ts: lazy playwright-core loader + HeadlessEngineUnavailableError
- src/browser/index.ts: loadBrowserEngineDetailed({ prefer, strict }) with
  graceful fallback to text engine when playwright-core is missing

Phase 6:
- src/config/themes.ts: 4 new themes (cyber, nocolor, hicontrast, termux)
- src/types.ts: Theme adds optional warn/tool/command/diffAdd/diffDel/noColor
- pickThemeForCaps() picks nocolor / termux automatically when warranted

Wiring:
- src/app.tsx: Prompt -> PromptV2 with history ref; tool calls now render
  via tool/success/error/diff blocks; Cancelled goes to warn role

Tests (+25 new, 146 total):
- test/inputHistory.test.ts (5)
- test/diffView.test.ts (3)
- test/themes.test.ts (6)
- test/promptHelpers.test.ts (5)
- test/headlessEngine.test.ts (5)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…palette

Phase 7a — UI mode wiring:
- src/tui/DebugPanel.tsx: live FSM/caps/MCP panel for uiMode=debug
- src/app.tsx: 'compact' & 'focus' hide banner; 'focus' hides StatusBar +
  LogoAnimator + bottom hint; 'debug' adds DebugPanel above logo

Phase 7b — auto theme selection:
- src/app.tsx: when config.theme is still DEFAULT_THEME, pickThemeForCaps()
  upgrades to 'nocolor' or 'termux' automatically based on capabilities

Phase 8 — long output protection:
- src/lib/transcriptVirtual.ts: virtualize() and clipLines() helpers
- src/app.tsx: tool outputs are clipped to 200 lines with a trailer marker
  before reaching the chat bubble

Phase 9 — search + palette commands:
- src/commands/search.ts: /search <query> greps ~/.fastcode/history JSONL
  and renders the most recent matches with timestamps
- src/commands/palette.ts: /palette groups every visible command into
  Session/Editing/Provider/Sandbox+MCP/Misc buckets with aliases

Phase 10 — error catalog:
- src/lib/errorCatalog.ts: explainError() and formatFriendlyError(),
  classifying 11 common failure shapes (FS_NOT_FOUND, FS_DENIED, PIPE_BROKEN,
  NET_TIMEOUT, NET_DNS, SANDBOX_DENIED, MCP_TRANSPORT, ABORTED, AUTH_FAILED,
  RATE_LIMITED, PROVIDER_5XX) with actionable hints
- src/app.tsx: tool failures and chat exceptions both run through
  explainError() before getting pushed as 'error' bubbles

Tests (+19 new, 184 total):
- test/transcriptVirtual.test.ts (5)
- test/errorCatalog.test.ts (10)
- test/searchCommand.test.ts (4)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@SimpleLittleDev SimpleLittleDev merged commit b149014 into main May 3, 2026
3 checks passed
@SimpleLittleDev
Copy link
Copy Markdown
Owner Author

v0.2 end-to-end TUI test — 5 passed, 2 real defects found

Tested all three v0.2 PRs (#3 + #4 + #5) live in xterm by driving the TUI via simulated keystrokes. Full report with screenshots: see attached test-report.md in the Devin session.

Lead — escalations first

🔴 DEFECT 1 — PromptV2 paste-burst corrupts multiline input
xterm bracketed-paste delivers \r (CR) as line separator, but PromptV2 splits buffer on \n only (src/tui/PromptV2.tsx:50). Pasting "line1\nline2" resulted in user echo bubble paste-line-Be-A (CR overwrite) and history JSONL stored literal \r. Suggested fix: normalize CR/CRLF/LF in the paste-burst branch.

// src/tui/PromptV2.tsx:~94
if (input && input.length > 1 && !key.ctrl && !key.meta) {
  insertText(input.replace(/\r\n?|\u2028|\u2029/g, "\n"));
  return;
}

🔴 DEFECT 2 — React duplicate-key warning on transcript pushes
Encountered two children with the same key, '16' (then '18', '4') logged repeatedly. Likely idRef.current += 1 race in a StrictMode double-render. Should use a stable counter inside the setTranscript updater.

⚠️ MISLEADING — placeholder says "Shift+Enter for newline" but xterm/most terminals don't forward shift modifier with Return. Either document the workaround (paste / Ctrl+J) or update placeholder.

ℹ️ DEAD CODE — /search empty-history branch: app.tsx:391 pushes prompt to history before slash dispatch, so by the time /search runs, the file always exists. The "No prompt history yet." branch in src/commands/search.ts is unreachable.

Test results

Tests verified live in TUI (click to collapse)
  • /palette — lists commands grouped into Session / Editing / Provider / Sandbox+MCP buckets (Misc empty by design)
  • /mode debug — DebugPanel mounts with state/label/terminal/flags/mcpServers/browserEngine/transcript rows
  • /mode focus — banner + StatusBar + LogoAnimator + bottom hint all hidden
  • /mode normal — StatusBar + LogoAnimator restored (banner intentionally stays hidden when transcript exceeds startup length)
  • /theme cyber — prompt arrow + status dot recolored cyan-bright; user bubble magenta-bright
  • /theme nocolor — all UI in plain white; cyan/magenta accents disappear
  • 🐛 PromptV2 multiline + history recall — partial: Up arrow recall works; multiline via Shift+Enter blocked by xterm; multiline via paste corrupts buffer (DEFECT 1)
  • /search palette — found 2 matches with timestamps from persisted JSONL
  • 🟡 /search empty-history fallback — untested: dead code path under normal flow
  • NO_COLOR=1 boot — auto-theme switches to nocolor; banner monochrome white; LogoAnimator uses ASCII tier > < > <
  • ✅ Idle logo animation — captured both frames ◆◇◆◇◆◇ and ◇◆◇◆◇◆ at the 800ms cycle
  • 🟡 Logo animation in thinking/executing/error/success/waiting states — untested visually: requires a live LLM call. Unit tests (test/logoAnimator.test.ts, test/stateMachine.test.ts) confirm FSM→frames mapping covers all 6 states.

Out of scope

  • Real LLM round-trip (no API key)
  • MCP server live connection (covered by test/mcpClient.test.ts)
  • Headless browser (opt-in; covered by test/headlessEngine.test.ts)

Devin session: https://app.devin.ai/sessions/0e73b43883c04a17b651ca6aa7dc1a5e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant